'''
Copyright (c) 2007 Bill Howe

 Permission is hereby granted, free of charge, to any person
 obtaining a copy of this software and associated documentation
 files (the "Software"), to deal in the Software without
 restriction, including without limitation the rights to use,
 copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the
 Software is furnished to do so, subject to the following
 conditions:

 The above copyright notice and this permission notice shall be
 included in all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 OTHER DEALINGS IN THE SOFTWARE.
==================

Utility functions

'''

import os
import re, time, datetime

import urllib
import subprocess

debug_print = False
import ConfigParser
import logging
import traceback
import logging.handlers as handlers

import sys, os.path

import glob, fnmatch
logging.raiseExceptions = False

path, name = os.path.split(sys.argv[0])
if not name: name = 'interpreter'
topcaller, ext = os.path.splitext(name)

syslogger = logging.getLogger('howpy')
try:
  filelog = handlers.RotatingFileHandler('/tmp/%s.log'  % (topcaller,), maxBytes=1024*1024*5, backupCount=10)
except IOError:
  filelog = handlers.RotatingFileHandler('%s.log'  % (topcaller,), maxBytes=1024*1024*5, backupCount=10)
  
filelog.setLevel(logging.INFO)
frmttr = logging.Formatter('%(levelname)s %(asctime)s: %(message)s')
filelog.setFormatter(frmttr)
syslogger.addHandler(filelog)

consolelog = logging.StreamHandler()
consolelog.setLevel(logging.INFO)
formatter = logging.Formatter('%(levelname)s %(asctime)s: %(message)s')
consolelog.setFormatter(formatter)
syslogger.addHandler(consolelog)

syslogger.setLevel(1)

def popen2(cmd):
  p = subprocess.Popen("cmd", shell=True, 
          stdin=subprocess.PIPE, stdout=subprocess.PIPE, close_fds=True)
  return (p.stdin, p.stdout)

def DebugOn(level=logging.DEBUG):
  SetLogLevel(level)

def DebugOff():
  SetLogLevel(logging.INFO)

def SetLogLevel(level):
  syslogger.setLevel(level)
  filelog.setLevel(level)
  consolelog.setLevel(level)

def logexception(prefix=""):
  msg = prefix + "".join(traceback.format_exception(*sys.exc_info()))
  error(msg)
  return msg

def debug(msg, level=logging.DEBUG):
  '''
Log a debugging message.
Second argument is the debugging level.  Usually 1-10.  Defaults to 10 (logging.DEBUG)
'''
  if level == logging.DEBUG:
    syslogger.debug(msg)
  else:
    syslogger.log(level, msg)
  consolelog.flush()

def warn(msg):
  syslogger.warn(msg)
def info(msg):
  syslogger.info(msg)
def critical(msg):
  syslogger.critical(msg)
def error(msg):
  syslogger.error(msg)
def profile(msg):
  '''
Log a profiling message.
Profiling information is rather verbose, so use a lower level than DEBUG
'''
  syslogger.log(5,msg)

def DM2DD(lat):
  deg, mn = lat.split()[0:2]
  if "W" in mn or "N" in mn or "E" in mn or "S" in mn:
    mn = mn[:-1]
  return float(deg) + float(mn)/60.0

def LoopCounter(ranges, start, end):
  assert(len(ranges) == len(start))
  for (s, (b,e)) in zip(start, ranges):
    if s < b or s > e:
      msg = "%s is not a valid counter for %s" % (start, ranges)
      raise ValueError(msg)

  sizes = [(s, e-s) for (s,e) in ranges]

  n = len(start)
  counter = list(start[:])

  while tuple(counter) != tuple(end):
    yield counter
    for i in range(1,n+1):
      offset, size = sizes[n-i]
      counter[n-i] = ((counter[n-i] - offset  + 1) % size) + offset
      if counter[n-i] != offset: break

def TimestepIterator(start, end, timestepseconds):
  # week number, day number, timestep number
  modelranges = [(1,53),(1,8),(0,86400/timestepseconds)]
  return LoopCounter(modelranges, start, end)

def UnQuote(s):
  return urllib.unquote(s)

def Quote(s):
  return urllib.quote(s.encode('utf-8'), "/:")

def LockFileName(expr):
  return '/tmp/.%s.lock' % (expr,)

def LockFile(expr):
  f = file(LockFileName(expr), 'w')
  
def Locked(expr):
  return os.path.exists(LockFileName(expr))

def UnlockFile(expr):
  if os.path.exists(LockFileName(expr)):
    os.remove(LockFileName(expr))

def Ping(host, n=1):
  cmd = "ping -W 4 -c %s %s" % (n, host)
  cout, cin = popen2.popen2(cmd)
  response = cout.read()
  m = re.search("(\d+)% packet loss, time (\d+)ms", response)
  if not m:
    # ping did not return; timed out
    return False
  else:
    if int(m.groups(0)[0]) == 100:
      return False
    else:
      return m.groups(0)[1]

def GetCmdLine():
   '''get the exact command line of the current process.
Useful for doing exact matching with AlreadyRunning.'''
   pid = os.getpid()
   f = file('/proc/%s/cmdline' % pid)
   c = f.read()
   c = c.replace('\x00', ' ').strip()
   return c

def AlreadyRunning(expr, search=True):
  '''Return count of processes matching expr.  Unix Only.
Inspects the /proc file system directly to avoid parsing ps output.

Added a 'search' argument to AlreadyRunning. 

search argument:  When True (the default), the expression is applied
using search semantics (see python re module).  When False, 
the expression is applied using 'ma
tch' semantics.

'''
  fs = glob.glob('/proc/[1-9]*/cmdline')

  if search: expr = '*%s*' % (expr,)

  n=0
  debug("AlreadyRunning? %s" % (expr,))
  for fn in fs:
    try:
      f = file(fn)
    except:
      debug("file %s missing" % fn)
    c = f.read()
    c = c.replace('\x00', ' ').strip()
    if fnmatch.fnmatch(c, expr):
      debug("match found for %s:%s" % (fn,c))
      n+=1
   

  return n
 
def SendMail(recipients, sender, subject, body):
  '''Send an email.  From 
http://www.thinkspot.net/sheila/article.php?story=20040822174141155
'''
  import smtplib
  smtpserver = 'ambcs01.stccmop.org'
  AUTHREQUIRED = 0 # if you need to use SMTP AUTH set to 1
  smtpuser = ''  # for SMTP AUTH, set SMTP username here
  smtppass = ''  # for SMTP AUTH, set SMTP password here

  RECIPIENTS = recipients
  SENDER = sender

  session = smtplib.SMTP(smtpserver)
  if AUTHREQUIRED:
    session.login(smtpuser, smtppass)

  mssg = "Subject: %s\r\n"
  mssg += "From: %s\r\nTo: %s\r\n\r\n"
  mssg = mssg % (subject, sender, ", ".join(RECIPIENTS))
  mssg += "%s" % (body,)
  smtpresult = session.sendmail(SENDER, RECIPIENTS, mssg)

  if smtpresult:
    errstr = ""
    for recip in smtpresult.keys():
        errstr = """Could not delivery mail to: %s

Server said: %s
%s

%s""" % (recip, smtpresult[recip][0], smtpresult[recip][1], errstr)
    raise smtplib.SMTPException, errstr
